/*
 * @Author: 张宇飞
 * @Date: 2020-02-25 15:06:18
 * @LastEditors: 张宇飞
 * @LastEditTime: 2020-02-29 18:28:39
 * @Description: 演示线程产生死锁的几种情况
 * @Email: zhangyufei49@163.com
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "utils.h"

#define print_test_header() \
    fprintf(stderr, "\n------------ %s ------------\n\n", __func__)

typedef struct {
    // 这里保存的是一个互斥锁数组
    pthread_mutex_t* mutexes;
    int cur;
} loop_ref_lock_t;

// 对同一互斥量在同一个线程内默认参数的情况下加锁两次将造成死锁
static void* lock_mutex_twice(void* arg) {
    printf("测试在同一个线程内对一个互斥量加锁两次造成死锁\n");
    pthread_mutex_t* mutex = (pthread_mutex_t*)arg;
    assert(0 == pthread_mutex_lock(mutex));
    printf("\t第一次加锁完成，现在进行第二次加锁，这里将死锁\n");
    assert(0 == pthread_mutex_lock(mutex));
    return NULL;
}

static void* loop_ref(void* arg) {
    loop_ref_lock_t* lock = (loop_ref_lock_t*)arg;
    int another = !lock->cur;
    pthread_mutex_t* mutex = &(lock->mutexes[lock->cur]);

    assert(0 == pthread_mutex_lock(mutex));
    fprintf(stderr, "\t线程 %p 对锁 %d 已加锁\n", (void*)pthread_self(),
            lock->cur);
    sleep(1);
    fprintf(stderr, "\t线程 %p 要获取锁 %d\n", (void*)pthread_self(), another);
    pthread_mutex_lock(&(lock->mutexes[lock->cur]));
    return NULL;
}

static void test_lock_mutex_twice() {
    print_test_header();
    //  创建一个锁
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    run_threads_with_args(lock_mutex_twice, &mutex, NULL);
}

static void test_loop_ref() {
    print_test_header();
    pthread_mutex_t mutexes[2] = {PTHREAD_MUTEX_INITIALIZER,
                                  PTHREAD_MUTEX_INITIALIZER};
    loop_ref_lock_t lrla = {.mutexes = mutexes, .cur = 0};
    loop_ref_lock_t lrlb = {.mutexes = mutexes, .cur = 1};

    run_threads_with_args(loop_ref, &lrla, loop_ref, &lrlb, NULL);
}

int main(int argc, char const* argv[]) {
    if (argc != 2) {
        printf("usage: %s <0 | 1> 选择一种造成死锁的方式\n", argv[0]);
        printf(
            "0: 互斥量锁2次（默认行为。参数出错的情况下也将运行这个例子）\n");
        printf("1: 环形引用\n");
        return -1;
    }
    int w = atoi(argv[1]);
    if (w) {
        test_loop_ref();
    } else {
        test_lock_mutex_twice();
    }
    return 0;
}
